<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Three.js Fuzzy Hover Selection</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        body {
            margin: 0;
            overflow: hidden;
        }

        canvas {
            display: block;
        }

        .controls {
            position: absolute;
            top: 10px;
            left: 10px;
            background-color: rgba(0, 0, 0, 0.5);
            color: white;
            padding: 10px;
            border-radius: 5px;
            font-family: Arial, sans-serif;
            font-size: 14px;
        }

        .controls div {
            margin-bottom: 10px;
        }

        button {
            padding: 5px 10px;
            margin-right: 5px;
        }

        .zoom-controls {
            position: absolute;
            bottom: 20px;
            right: 20px;
            background-color: rgba(0, 0, 0, 0.5);
            color: white;
            padding: 10px;
            border-radius: 5px;
            z-index: 10;
        }
    </style>

    <script type="importmap">
        {
            "imports": {
                "three": "./threejs/build/three.module.js",
                "three/addons/": "./threejs/examples/jsm/"
            }
        }
    </script>
</head>

<body>
    <div class="controls">
        <div>
            <label>线条颜色: </label>
            <input type="color" id="colorPicker" value="#ffffff">
        </div>
        <div>
            <label>线条宽度: </label>
            <input type="range" id="widthSlider" min="1" max="20" value="5">
            <span id="widthValue">5</span> px
        </div>
        <div>
            <label>选中灵敏度: </label>
            <input type="range" id="sensitivitySlider" min="5" max="50" value="15">
            <span id="sensitivityValue">15</span> px
        </div>
        <div>
            <button id="undoButton">撤销</button>
            <button id="clearButton">清除</button>
        </div>
        <div>
            <label>绘制模式: </label>
            <select id="drawMode">
                <option value="line">直线</option>
                <option value="rect">矩形</option>
                <option value="circle">圆形</option>
            </select>
        </div>
    </div>
    <div class="zoom-controls">
        <button id="resetViewButton">重置视图</button>
    </div>

    <script type="module">
        import * as THREE from 'three';
        import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

        // 初始化场景、相机和渲染器
        const scene = new THREE.Scene();
        scene.background = new THREE.Color(0x000000);

        // 设置正交相机
        let camera, initialCameraPosition, initialCameraZoom;
        function setupCamera() {
            const width = window.innerWidth;
            const height = window.innerHeight;
            camera = new THREE.OrthographicCamera(
                -width / 2, width / 2, height / 2, -height / 2, 0.1, 10000
            );
            camera.position.z = 10;
            initialCameraPosition = camera.position.clone();
            initialCameraZoom = 1;
        }
        setupCamera();

        const renderer = new THREE.WebGLRenderer({ antialias: true });
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setClearColor(0x000000, 1);
        document.body.appendChild(renderer.domElement);

        // 初始化控制器
        const controls = new OrbitControls(camera, renderer.domElement);
        controls.enableDamping = true;
        controls.dampingFactor = 0.05;
        controls.enableZoom = true;
        controls.enableRotate = false;
        controls.enablePan = true;
        controls.screenSpacePanning = false;

        // 线条状态管理
        let currentColor = new THREE.Color('#ffffff').getHex();
        let currentWidth = 5; // 可见线条宽度
        let selectSensitivity = 15; // 碰撞线宽度（控制选中灵敏度）
        let drawMode = 'line';
        let isDrawing = false;
        let startPoint = null;
        const lines = []; // 存储可见线条
        const collisionLines = []; // 存储碰撞线（用于检测）
        let currentLine = null; // 当前可见预览线
        let currentCollisionLine = null; // 当前碰撞预览线
        let points = [];
        let selectedLine = null;
        const raycaster = new THREE.Raycaster();
        const mouse = new THREE.Vector2();

        // 窗口大小调整
        window.addEventListener('resize', () => {
            const width = window.innerWidth;
            const height = window.innerHeight;
            camera.left = -width / 2;
            camera.right = width / 2;
            camera.top = height / 2;
            camera.bottom = -height / 2;
            camera.updateProjectionMatrix();
            renderer.setSize(width, height);
        });

        // 鼠标左键：绘制或取消选中
        renderer.domElement.addEventListener('click', (event) => {
            if (event.button !== 0) return;
            if (selectedLine) {
                unhighlightLine(selectedLine);
                selectedLine = null;
                return;
            }
            const mousePos = getWorldPosition(event.clientX, event.clientY);
            if (!isDrawing) {
                isDrawing = true;
                startPoint = mousePos.clone();
                points = [startPoint, mousePos.clone()];
                createNewLine(true); // 创建预览线（含碰撞线）
                updatePreview(mousePos);
            } else {
                isDrawing = false;
                if (drawMode === 'line') {
                    points = [startPoint, mousePos.clone()];
                } else if (drawMode === 'rect') {
                    generateRectanglePoints(startPoint, mousePos);
                } else if (drawMode === 'circle') {
                    generateCirclePoints(startPoint, mousePos);
                }
                replaceWithSolidLine();
            }
        });

        // 鼠标右键：取消操作
        renderer.domElement.addEventListener('contextmenu', (event) => {
            event.preventDefault();
            if (isDrawing) {
                scene.remove(currentLine);
                scene.remove(currentCollisionLine);
                disposeLine(currentLine, currentCollisionLine);
                currentLine = null;
                currentCollisionLine = null;
                isDrawing = false;
                points = [];
            } else if (selectedLine) {
                unhighlightLine(selectedLine);
                selectedLine = null;
            }
        });

        // 鼠标移动：更新预览 + 模糊检测
        renderer.domElement.addEventListener('mousemove', (event) => {
            mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
            mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

            if (isDrawing) {
                const mousePos = getWorldPosition(event.clientX, event.clientY);
                updatePreview(mousePos);
            } else {
                detectLineHover();
            }
        });

        // 检测鼠标悬浮的线条（基于碰撞线）
        function detectLineHover() {
            raycaster.setFromCamera(mouse, camera);
            // 仅检测碰撞线（线宽更大，容错率更高）
            const intersects = raycaster.intersectObjects(collisionLines);

            if (intersects.length > 0) {
                const hoveredCollisionLine = intersects[0].object;
                const hoveredLine = hoveredCollisionLine.userData.visibleLine;
                if (hoveredLine !== selectedLine) {
                    if (selectedLine) unhighlightLine(selectedLine);
                    highlightLine(hoveredLine);
                    selectedLine = hoveredLine;
                }
            } else {
                if (selectedLine) {
                    unhighlightLine(selectedLine);
                    selectedLine = null;
                }
            }
        }

        // 高亮/取消高亮可见线
        function highlightLine(line) {
            if (!line.userData.originalMaterial) {
                line.userData.originalMaterial = {
                    color: line.material.color.getHex(),
                    linewidth: line.material.linewidth
                };
            }
            line.material.color.set(0xffff00); // 黄色高亮
            line.material.linewidth = line.userData.originalMaterial.linewidth + 2;
        }

        function unhighlightLine(line) {
            if (line.userData.originalMaterial) {
                line.material.color.set(line.userData.originalMaterial.color);
                line.material.linewidth = line.userData.originalMaterial.linewidth;
            }
        }

        // 控制栏事件
        document.getElementById('colorPicker').addEventListener('change', (e) => {
            currentColor = new THREE.Color(e.target.value).getHex();
            if (currentLine) currentLine.material.color.set(currentColor);
        });

        const widthSlider = document.getElementById('widthSlider');
        const widthValue = document.getElementById('widthValue');
        widthSlider.addEventListener('input', (e) => {
            currentWidth = parseInt(e.target.value);
            widthValue.textContent = currentWidth;
            if (currentLine) currentLine.material.linewidth = currentWidth;
        });

        // 选中灵敏度调节（直接控制碰撞线宽度）
        const sensitivitySlider = document.getElementById('sensitivitySlider');
        const sensitivityValue = document.getElementById('sensitivityValue');
        sensitivitySlider.addEventListener('input', (e) => {
            selectSensitivity = parseInt(e.target.value);
            sensitivityValue.textContent = selectSensitivity;
            // 实时更新当前预览的碰撞线宽度
            if (currentCollisionLine) {
                currentCollisionLine.material.linewidth = selectSensitivity;
            }
            // 更新已绘制线条的碰撞线宽度
            collisionLines.forEach(line => {
                line.material.linewidth = selectSensitivity;
            });
        });

        document.getElementById('clearButton').addEventListener('click', () => {
            lines.forEach(line => {
                scene.remove(line);
                line.geometry.dispose();
                line.material.dispose();
            });
            collisionLines.forEach(line => {
                scene.remove(line);
                line.geometry.dispose();
                line.material.dispose();
            });
            lines.length = 0;
            collisionLines.length = 0;
            currentLine = null;
            currentCollisionLine = null;
            selectedLine = null;
            isDrawing = false;
            points = [];
        });

        document.getElementById('undoButton').addEventListener('click', () => {
            if (lines.length === 0) return;
            const lastLine = lines.pop();
            const lastCollisionLine = collisionLines.pop();
            scene.remove(lastLine);
            scene.remove(lastCollisionLine);
            lastLine.geometry.dispose();
            lastCollisionLine.geometry.dispose();
            lastLine.material.dispose();
            lastCollisionLine.material.dispose();
            if (selectedLine === lastLine) {
                unhighlightLine(selectedLine);
                selectedLine = null;
            }
        });

        document.getElementById('drawMode').addEventListener('change', (e) => {
            drawMode = e.target.value;
            isDrawing = false;
            if (currentLine) {
                scene.remove(currentLine);
                scene.remove(currentCollisionLine);
                disposeLine(currentLine, currentCollisionLine);
                currentLine = null;
                currentCollisionLine = null;
            }
        });

        document.getElementById('resetViewButton').addEventListener('click', () => {
            camera.position.copy(initialCameraPosition);
            camera.zoom = initialCameraZoom;
            camera.updateProjectionMatrix();
            controls.reset();
        });

        // 创建线条：同时生成可见线和碰撞线
        function createNewLine(isPreview) {
            // 1. 可见线（用户看到的线条）
            const visibleGeometry = new THREE.BufferGeometry().setFromPoints(points);
            const visibleMaterial = isPreview
                ? new THREE.LineDashedMaterial({
                    color: currentColor,
                    linewidth: currentWidth,
                    dashSize: currentWidth * 3,
                    gapSize: currentWidth * 2
                })
                : new THREE.LineBasicMaterial({
                    color: currentColor,
                    linewidth: currentWidth
                });
            const visibleLine = new THREE.Line(visibleGeometry, visibleMaterial);
            if (isPreview) visibleLine.computeLineDistances();

            // 2. 碰撞线（用于检测，宽度=灵敏度，完全透明）
            const collisionGeometry = new THREE.BufferGeometry().setFromPoints(points);
            const collisionMaterial = new THREE.LineBasicMaterial({
                color: 0x000000,
                linewidth: selectSensitivity, // 碰撞线宽度=灵敏度
                transparent: true,
                opacity: 0, // 完全透明，视觉不可见
                depthTest: false // 不参与深度检测，避免影响其他元素
            });
            const collisionLine = new THREE.Line(collisionGeometry, collisionMaterial);
            // 绑定可见线和碰撞线
            collisionLine.userData.visibleLine = visibleLine;
            visibleLine.userData.collisionLine = collisionLine;

            scene.add(visibleLine);
            scene.add(collisionLine);

            currentLine = visibleLine;
            currentCollisionLine = collisionLine;

            return { visibleLine, collisionLine };
        }

        // 更新预览：同步更新可见线和碰撞线
        function updatePreview(mousePos) {
            if (!currentLine || !currentCollisionLine) return;

            // 更新点集
            if (drawMode === 'line') {
                points = [startPoint, mousePos.clone()];
            } else if (drawMode === 'rect') {
                generateRectanglePoints(startPoint, mousePos);
            } else if (drawMode === 'circle') {
                generateCirclePoints(startPoint, mousePos);
            }

            // 更新可见线
            currentLine.geometry.dispose();
            currentLine.geometry.setFromPoints(points);
            currentLine.computeLineDistances();

            // 更新碰撞线（与可见线同步形状，宽度由灵敏度控制）
            currentCollisionLine.geometry.dispose();
            currentCollisionLine.geometry.setFromPoints(points);
            currentCollisionLine.computeLineDistances();
        }

        // 替换为实线：保存可见线和碰撞线
        function replaceWithSolidLine() {
            if (!currentLine || !currentCollisionLine) return;

            scene.remove(currentLine);
            scene.remove(currentCollisionLine);
            // 创建最终线条（可见线+碰撞线）
            const { visibleLine, collisionLine } = createNewLine(false);
            lines.push(visibleLine);
            collisionLines.push(collisionLine);
            // 重置预览状态
            currentLine = null;
            currentCollisionLine = null;
        }

        // 生成矩形点集
        function generateRectanglePoints(p1, p2) {
            points = [
                new THREE.Vector3(p1.x, p1.y, 0),
                new THREE.Vector3(p2.x, p1.y, 0),
                new THREE.Vector3(p2.x, p2.y, 0),
                new THREE.Vector3(p1.x, p2.y, 0),
                new THREE.Vector3(p1.x, p1.y, 0) // 闭合矩形
            ];
        }

        // 生成圆形点集
        function generateCirclePoints(center, edgePoint) {
            const radius = center.distanceTo(edgePoint);
            points = [];
            const segments = 36; // 36段近似圆形
            for (let i = 0; i <= segments; i++) {
                const angle = (i / segments) * Math.PI * 2;
                points.push(new THREE.Vector3(
                    center.x + radius * Math.cos(angle),
                    center.y + radius * Math.sin(angle),
                    0
                ));
            }
        }

        // 辅助函数：释放线条资源
        function disposeLine(visibleLine, collisionLine) {
            visibleLine.geometry.dispose();
            collisionLine.geometry.dispose();
            visibleLine.material.dispose();
            collisionLine.material.dispose();
        }

        // 辅助函数：获取鼠标世界坐标
        function getWorldPosition(screenX, screenY) {
            const mouse = new THREE.Vector2();
            mouse.x = (screenX / window.innerWidth) * 2 - 1;
            mouse.y = -(screenY / window.innerHeight) * 2 + 1;
            const raycaster = new THREE.Raycaster();
            raycaster.setFromCamera(mouse, camera);
            const plane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0);
            const intersection = new THREE.Vector3();
            raycaster.ray.intersectPlane(plane, intersection);
            return intersection;
        }

        // 动画循环
        function animate() {
            requestAnimationFrame(animate);
            controls.update();
            renderer.render(scene, camera);
        }
        animate();
    </script>
</body>

</html>